Popular Searches
Popular Course Categories
Popular Courses

Reading and controlling user input

Reading and controlling user input

Flutter Forms & User Input

Reading and Controlling User Input in Flutter – Detailed Notes

User input is an essential part of interactive Flutter applications. Apps commonly need to accept information such as names, email addresses, passwords, search queries, messages, addresses, and other data from users.

Flutter provides widgets such as TextField and TextFormField for accepting text input. A TextEditingController can then be used to read, modify, clear, and listen to the text entered by the user. :contentReference[oaicite:0]{index=0}


1. What is User Input?

User input is information provided by a user through an application's interface. In Flutter, user input can come from text fields, buttons, checkboxes, switches, dropdowns, sliders, and other interactive widgets.

Common Examples of Text Input

  • User name
  • Email address
  • Password
  • Phone number
  • Search keywords
  • Address
  • Comments
  • Messages
  • Product information

2. TextField for Taking User Input

TextField is one of the most commonly used Flutter widgets for accepting text input.

TextField(
  decoration: InputDecoration(
    labelText: 'Enter your name',
    border: OutlineInputBorder(),
  ),
)

Flutter's TextField supports decoration such as labels, hints, icons, borders, and error-related UI. :contentReference[oaicite:1]{index=1}


3. TextFormField for Structured Forms

TextFormField is useful when text input is part of a Form and needs validation.

TextFormField(
  decoration: InputDecoration(
    labelText: 'Email',
    border: OutlineInputBorder(),
  ),
)

TextFormField integrates a text field with Flutter's Form and supports validation through a validator function. :contentReference[oaicite:2]{index=2}


4. Reading User Input with TextEditingController

The most common way to programmatically read text entered into a TextField is by using TextEditingController.

final nameController = TextEditingController();

TextField(
  controller: nameController,
)

The current input can then be accessed using:

String name = nameController.text;
print(name);

Flutter's official documentation recommends creating a controller, assigning it to the text field, and reading the current value through the controller's text property. :contentReference[oaicite:3]{index=3}


5. Basic Complete Example

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: const UserInputScreen(),
    );
  }
}

class UserInputScreen extends StatefulWidget {
  const UserInputScreen({super.key});

  @override
  State createState() => _UserInputScreenState();
}

class _UserInputScreenState extends State {
  final nameController = TextEditingController();

  @override
  void dispose() {
    nameController.dispose();
    super.dispose();
  }

  void readInput() {
    final name = nameController.text;
    print('User Name: $name');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('User Input'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(
              controller: nameController,
              decoration: const InputDecoration(
                labelText: 'Enter your name',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: readInput,
              child: const Text('Read Input'),
            ),
          ],
        ),
      ),
    );
  }
}

6. Understanding TextEditingController

TextEditingController controls the editable text associated with a text field. When the user changes the text, the controller's value changes and its listeners can be notified. You can also change the controller's text programmatically. :contentReference[oaicite:4]{index=4}

Important Controller Operations

Operation Code Purpose
Read text controller.text Gets current user input
Set text controller.text = 'Hello' Changes input programmatically
Clear text controller.clear() Removes input
Listen for changes controller.addListener() Detects controller changes
Read editing value controller.value Gets complete editing state
Read selection controller.selection Gets current text selection
Release resources controller.dispose() Disposes the controller

7. Reading Input Using onChanged

For simple cases, onChanged can be used to receive the latest text whenever the user changes the field.

TextField(
  onChanged: (value) {
    print('Current input: $value');
  },
)

The callback runs whenever the text changes. Flutter's documentation identifies onChanged and TextEditingController as two common approaches for handling text changes. :contentReference[oaicite:5]{index=5}

Example: Live Character Count

TextField(
  maxLength: 100,
  onChanged: (value) {
    print('Characters: ${value.length}');
  },
)

8. onChanged vs TextEditingController

Feature onChanged TextEditingController
Detect text changes Yes Yes
Read current text Callback value controller.text
Change text programmatically No Yes
Clear field programmatically No Yes
Access selection No Yes
Listen from multiple places Less convenient Yes
Requires disposal No Yes when controller is owned by the widget

Use onChanged for simple change callbacks. Use a controller when your application needs broader control over the field's value. :contentReference[oaicite:6]{index=6}


9. Controlling User Input Programmatically

One of the major advantages of TextEditingController is that the application can modify the text field programmatically.

final nameController = TextEditingController();

nameController.text = 'Flutter Developer';

The connected text field will display the updated value.

Button Example

ElevatedButton(
  onPressed: () {
    nameController.text = 'Flutter Developer';
  },
  child: const Text('Set Default Text'),
)

10. Clearing User Input

Use the clear() method when the application needs to remove the current input.

nameController.clear();

Example

IconButton(
  onPressed: () {
    nameController.clear();
  },
  icon: const Icon(Icons.clear),
)

Clearing input is commonly used in search boxes, login forms, chat applications, and todo applications. Flutter's user-input tutorial also demonstrates clearing a controller after submitted input. :contentReference[oaicite:7]{index=7}


11. Checking Whether Input is Empty

if (nameController.text.isEmpty) {
  print('Please enter your name');
} else {
  print('Name: ${nameController.text}');
}

Using trim()

final name = nameController.text.trim();

if (name.isEmpty) {
  print('Name is required');
}

Using trim() is useful when whitespace at the beginning or end of the input should not be considered meaningful.


12. Setting Initial User Input

A controller can be initialized with an initial value.

final nameController = TextEditingController(
  text: 'John',
);

The connected field initially displays John. The controller constructor accepts an optional initial text value. :contentReference[oaicite:8]{index=8}


13. Listening to User Input Changes

Use addListener() when you want a function to be called whenever the controller's value changes.

final searchController = TextEditingController();

@override
void initState() {
  super.initState();

  searchController.addListener(() {
    print(searchController.text);
  });
}

The listener can be removed explicitly, although disposing the controller also removes its registered listeners.

@override
void dispose() {
  searchController.dispose();
  super.dispose();
}

Flutter's official documentation demonstrates registering listeners in initState() and disposing the controller in dispose(). :contentReference[oaicite:9]{index=9}


14. Reading User Input on Button Press

final emailController = TextEditingController();

ElevatedButton(
  onPressed: () {
    final email = emailController.text.trim();
    print('Email: $email');
  },
  child: const Text('Submit'),
)

This pattern is commonly used when submitting login, registration, contact, or search forms.


15. Reading Multiple User Inputs

Applications frequently need to read multiple fields at once.

final nameController = TextEditingController();
final emailController = TextEditingController();
final phoneController = TextEditingController();

void submitForm() {
  final name = nameController.text.trim();
  final email = emailController.text.trim();
  final phone = phoneController.text.trim();

  print('Name: $name');
  print('Email: $email');
  print('Phone: $phone');
}

16. Complete User Information Example

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: const UserFormScreen(),
    );
  }
}

class UserFormScreen extends StatefulWidget {
  const UserFormScreen({super.key});

  @override
  State createState() => _UserFormScreenState();
}

class _UserFormScreenState extends State {
  final nameController = TextEditingController();
  final emailController = TextEditingController();
  final phoneController = TextEditingController();

  @override
  void dispose() {
    nameController.dispose();
    emailController.dispose();
    phoneController.dispose();
    super.dispose();
  }

  void submit() {
    final name = nameController.text.trim();
    final email = emailController.text.trim();
    final phone = phoneController.text.trim();

    if (name.isEmpty || email.isEmpty || phone.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Please fill all fields'),
        ),
      );
      return;
    }

    showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: const Text('User Information'),
          content: Text(
            'Name: $name\nEmail: $email\nPhone: $phone',
          ),
          actions: [
            TextButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: const Text('OK'),
            ),
          ],
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('User Information'),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(
              controller: nameController,
              decoration: const InputDecoration(
                labelText: 'Full Name',
                prefixIcon: Icon(Icons.person),
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 16),
            TextField(
              controller: emailController,
              keyboardType: TextInputType.emailAddress,
              decoration: const InputDecoration(
                labelText: 'Email',
                prefixIcon: Icon(Icons.email),
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 16),
            TextField(
              controller: phoneController,
              keyboardType: TextInputType.phone,
              decoration: const InputDecoration(
                labelText: 'Phone Number',
                prefixIcon: Icon(Icons.phone),
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 20),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: submit,
                child: const Text('Submit'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

17. Reading Email Input

For email input, use an appropriate keyboard type and controller.

final emailController = TextEditingController();

TextField(
  controller: emailController,
  keyboardType: TextInputType.emailAddress,
  decoration: const InputDecoration(
    labelText: 'Email Address',
    border: OutlineInputBorder(),
  ),
)

Read the value using:

final email = emailController.text.trim();

18. Reading Password Input

final passwordController = TextEditingController();

TextField(
  controller: passwordController,
  obscureText: true,
  decoration: const InputDecoration(
    labelText: 'Password',
    border: OutlineInputBorder(),
  ),
)

Read the password when the user submits the form:

final password = passwordController.text;

In real applications, avoid unnecessarily printing or logging passwords.


19. Reading Search Input

A search field can use a controller to retrieve and control the search query.

final searchController = TextEditingController();

TextField(
  controller: searchController,
  decoration: InputDecoration(
    hintText: 'Search products',
    prefixIcon: const Icon(Icons.search),
    suffixIcon: IconButton(
      icon: const Icon(Icons.clear),
      onPressed: () {
        searchController.clear();
      },
    ),
    border: const OutlineInputBorder(),
  ),
)

Search Function

void searchProducts() {
  final query = searchController.text.trim();

  if (query.isEmpty) {
    return;
  }

  print('Searching for: $query');
}

20. Reading Multiline Input

Use maxLines when users need to enter longer content such as comments or messages.

final messageController = TextEditingController();

TextField(
  controller: messageController,
  maxLines: 5,
  decoration: const InputDecoration(
    labelText: 'Your Message',
    border: OutlineInputBorder(),
  ),
)

Reading the Message

final message = messageController.text.trim();
print(message);

21. Controlling Maximum Input Length

Use maxLength to restrict the number of characters accepted by the field.

TextField(
  controller: messageController,
  maxLength: 200,
  decoration: const InputDecoration(
    labelText: 'Message',
  ),
)

This can be useful for usernames, short descriptions, comments, and messages.


22. Handling Submitted Input

The onSubmitted callback runs when the user submits the field using the keyboard action.

TextField(
  controller: searchController,
  onSubmitted: (value) {
    print('Submitted: $value');
  },
)

Clearing After Submission

TextField(
  controller: searchController,
  onSubmitted: (_) {
    print(searchController.text);
    searchController.clear();
  },
)

Flutter's user-input tutorial demonstrates using onSubmitted together with a controller to capture and then clear submitted text. :contentReference[oaicite:10]{index=10}


23. Controlling Focus

Good input handling often includes focus management. A FocusNode can be connected to a text field and used to request focus programmatically.

final nameController = TextEditingController();
final nameFocusNode = FocusNode();

TextField(
  controller: nameController,
  focusNode: nameFocusNode,
)

Requesting Focus

nameFocusNode.requestFocus();

Disposing FocusNode

@override
void dispose() {
  nameController.dispose();
  nameFocusNode.dispose();
  super.dispose();
}

Flutter recommends managing long-lived focus nodes with the widget's state lifecycle. :contentReference[oaicite:11]{index=11}


24. Moving Focus Between Fields

For forms with multiple fields, focus can be moved from one field to another.

final nameFocusNode = FocusNode();
final emailFocusNode = FocusNode();

TextField(
  focusNode: nameFocusNode,
  textInputAction: TextInputAction.next,
  onSubmitted: (_) {
    emailFocusNode.requestFocus();
  },
)

TextField(
  focusNode: emailFocusNode,
  textInputAction: TextInputAction.done,
)

25. Validating User Input

Reading input and validating input are related but separate tasks. A form can validate user input before processing it.

final formKey = GlobalKey();
final emailController = TextEditingController();

Form(
  key: formKey,
  child: TextFormField(
    controller: emailController,
    validator: (value) {
      if (value == null || value.trim().isEmpty) {
        return 'Email is required';
      }
      return null;
    },
  ),
)

Validate Before Submission

if (formKey.currentState!.validate()) {
  final email = emailController.text.trim();
  print('Valid email: $email');
}

Flutter's forms documentation uses Form, GlobalKey, and TextFormField.validator to validate user input. :contentReference[oaicite:12]{index=12}


26. Reading Input and Sending it to an API

User input can also be passed to a networking function when the user submits a form.

final titleController = TextEditingController();

void sendData() {
  final title = titleController.text.trim();

  if (title.isEmpty) {
    return;
  }

  createData(title);
}

Future createData(String title) async {
  print('Sending title: $title');
}

Flutter's networking cookbook demonstrates retrieving a value from a TextField through a controller and passing that value into a network request. :contentReference[oaicite:13]{index=13}


27. Practical Login Input Example

class LoginScreen extends StatefulWidget {
  const LoginScreen({super.key});

  @override
  State createState() => _LoginScreenState();
}

class _LoginScreenState extends State {
  final emailController = TextEditingController();
  final passwordController = TextEditingController();

  @override
  void dispose() {
    emailController.dispose();
    passwordController.dispose();
    super.dispose();
  }

  void login() {
    final email = emailController.text.trim();
    final password = passwordController.text;

    if (email.isEmpty || password.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Please enter email and password'),
        ),
      );
      return;
    }

    print('Login requested for: $email');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Login'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: emailController,
              keyboardType: TextInputType.emailAddress,
              decoration: const InputDecoration(
                labelText: 'Email',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 16),
            TextField(
              controller: passwordController,
              obscureText: true,
              decoration: const InputDecoration(
                labelText: 'Password',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 20),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: login,
                child: const Text('Login'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

28. Practical Search Example with Live Input

class SearchScreen extends StatefulWidget {
  const SearchScreen({super.key});

  @override
  State createState() => _SearchScreenState();
}

class _SearchScreenState extends State {
  final searchController = TextEditingController();

  final products = [
    'Flutter Course',
    'Dart Course',
    'Android Course',
    'iOS Course',
    'Web Development Course',
  ];

  List results = [];

  @override
  void initState() {
    super.initState();
    results = products;

    searchController.addListener(filterProducts);
  }

  void filterProducts() {
    final query = searchController.text.toLowerCase().trim();

    setState(() {
      results = products
          .where(
            (product) => product.toLowerCase().contains(query),
          )
          .toList();
    });
  }

  @override
  void dispose() {
    searchController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Product Search'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(
              controller: searchController,
              decoration: InputDecoration(
                hintText: 'Search products',
                prefixIcon: const Icon(Icons.search),
                suffixIcon: IconButton(
                  icon: const Icon(Icons.clear),
                  onPressed: () {
                    searchController.clear();
                  },
                ),
                border: const OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 16),
            Expanded(
              child: ListView.builder(
                itemCount: results.length,
                itemBuilder: (context, index) {
                  return ListTile(
                    title: Text(results[index]),
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

This demonstrates how user input can be read continuously and used to update application data while the user types.


29. Resetting User Input

When a form contains multiple controllers, create a reset function.

void resetForm() {
  nameController.clear();
  emailController.clear();
  phoneController.clear();
  passwordController.clear();
}

Then connect it to a button:

OutlinedButton(
  onPressed: resetForm,
  child: const Text('Reset'),
)

30. Reading and Controlling Input with a Form

class RegistrationScreen extends StatefulWidget {
  const RegistrationScreen({super.key});

  @override
  State createState() => _RegistrationScreenState();
}

class _RegistrationScreenState extends State {
  final formKey = GlobalKey();
  final nameController = TextEditingController();
  final emailController = TextEditingController();

  @override
  void dispose() {
    nameController.dispose();
    emailController.dispose();
    super.dispose();
  }

  void submit() {
    if (!formKey.currentState!.validate()) {
      return;
    }

    final name = nameController.text.trim();
    final email = emailController.text.trim();

    print('Name: $name');
    print('Email: $email');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Registration'),
      ),
      body: Form(
        key: formKey,
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              TextFormField(
                controller: nameController,
                decoration: const InputDecoration(
                  labelText: 'Name',
                  border: OutlineInputBorder(),
                ),
                validator: (value) {
                  if (value == null || value.trim().isEmpty) {
                    return 'Name is required';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 16),
              TextFormField(
                controller: emailController,
                keyboardType: TextInputType.emailAddress,
                decoration: const InputDecoration(
                  labelText: 'Email',
                  border: OutlineInputBorder(),
                ),
                validator: (value) {
                  if (value == null || value.trim().isEmpty) {
                    return 'Email is required';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 20),
              SizedBox(
                width: double.infinity,
                child: ElevatedButton(
                  onPressed: submit,
                  child: const Text('Register'),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

31. Controller Lifecycle

A controller should normally follow the lifecycle of the stateful widget that owns it.

  1. Create: Declare the controller as a state field.
  2. Connect: Pass it to the TextField or TextFormField.
  3. Read: Access the value using controller.text.
  4. Control: Modify or clear the value when necessary.
  5. Listen: Use addListener() when change notifications are required.
  6. Dispose: Call dispose() when the widget is removed.
class ExampleState extends State {
  final controller = TextEditingController();

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: controller,
    );
  }
}

Flutter's documentation specifically recommends disposing a controller when it is no longer needed. :contentReference[oaicite:14]{index=14}


32. Common Mistakes

Mistake 1: Forgetting dispose()

final controller = TextEditingController();

Better:

@override
void dispose() {
  controller.dispose();
  super.dispose();
}

Mistake 2: Creating the Controller Inside build()

@override
Widget build(BuildContext context) {
  final controller = TextEditingController();

  return TextField(
    controller: controller,
  );
}

A controller should generally have an appropriate lifecycle instead of being recreated every time build() runs.

Mistake 3: Not Checking Empty Input

final name = controller.text.trim();

if (name.isEmpty) {
  return;
}

Mistake 4: Modifying Text Inside a Listener Without Care

controller.addListener(() {
  controller.text = controller.text.toUpperCase();
});

Changing the controller inside its own listener can cause repeated notifications or loops. Flutter's API documentation specifically warns about this situation. :contentReference[oaicite:15]{index=15}


33. Best Practices for Reading and Controlling User Input

  • Use TextField for general text input.
  • Use TextFormField when the input belongs to a validated form.
  • Use TextEditingController when the application needs to read or control the input programmatically.
  • Use onChanged for simple text-change callbacks.
  • Use onSubmitted when input should be processed after keyboard submission.
  • Use trim() where leading and trailing whitespace should not matter.
  • Use appropriate keyboard types such as email, phone, and number.
  • Use obscureText for password fields.
  • Use validation before processing important form data.
  • Dispose controllers when they are no longer needed.
  • Dispose FocusNode objects that your state owns.
  • Avoid unnecessary controller recreation.
  • Do not log sensitive information such as passwords.

34. Real-World Applications

Application Input Example Typical Technique
Login Email and password TextEditingController + validation
Registration Name, email, phone Multiple controllers + Form
Search Product keyword Controller + listener/onChanged
Chat Message Controller + onSubmitted
Todo Task name Controller + clear()
Profile Name and address Initial values + controllers
API form Data fields Controller + network request

35. Interview Questions

Q1. How can you read text entered in a TextField?

Use a TextEditingController and read its text property.

final value = controller.text;

Q2. How can you detect changes in user input?

Use either the onChanged callback or a TextEditingController listener.

Q3. How do you clear a TextField?

controller.clear();

Q4. How do you set text programmatically?

controller.text = 'Flutter';

Q5. Why should TextEditingController be disposed?

It is a disposable object and should be cleaned up when the owning widget no longer needs it.

Q6. What is the difference between TextField and TextFormField?

TextField provides general text input, while TextFormField integrates with Form and provides form-oriented features such as validation.

Q7. What is the purpose of FocusNode?

FocusNode allows the application to manage which input field currently has keyboard focus and to request or change focus programmatically.


36. Quick Revision

  • TextField accepts text input.
  • TextFormField is useful for form-based input and validation.
  • TextEditingController provides programmatic control over text.
  • Read text with controller.text.
  • Set text with controller.text = 'value'.
  • Clear text with controller.clear().
  • Detect changes with onChanged or addListener().
  • Handle keyboard submission with onSubmitted.
  • Use FocusNode to control input focus.
  • Use Form and validators when structured validation is required.
  • Dispose controllers and focus nodes that your widget owns.
  • Always validate and safely process important user input.

37. Official Flutter Resources


38. JustAcademy Flutter Training Resources


39. Key Takeaways

Reading and controlling user input is a fundamental Flutter skill. TextField and TextFormField provide the input interface, while TextEditingController gives the application programmatic access to the entered text. Developers can read values with text, update them programmatically, clear them, listen for changes, handle submission, manage focus, validate form data, and pass collected values to application logic or APIs.

Understanding these concepts makes it possible to build practical Flutter applications such as login screens, registration forms, search interfaces, chat screens, profile forms, todo apps, and API-powered data-entry screens.

Previous Next
whatsapp